/**
 * 描述 函数工具箱，兼容Node,浏览器端
 * 时间：2022-04-11
 * 作者：自夏
 */
import { randomCodingCallback, systemType } from '../entity/UtilityBox_Type'

import { runTimeBrowser } from '../Interface/util_Interface';



/**
 * 区分系统分类装饰器
 * 获取当前系统运行时，并且根据当前运行是生成base64秘钥
 * @param system Window 
 * @returns 
 */
function systemV8AndNodeOS(system: Window | any) {
  return (taget: any, MethodName: any, desc: any) => {
    const browserEnv = 'navigator' in system, NodeV8Env = 'process' in system;
    let a, b, c, d, systenKey: string;
    if (browserEnv) {
      // const { appCodeName: String, appName: String, appVersion: String, languages: Array<String> } 
      const { appCodeName, appName, appVersion, languages } = system.navigator;
      a = appCodeName, b = appName, c = appVersion, d = languages
      systenKey = btoa(encodeURIComponent(`${a + b + c + d}`))
    }
    if (NodeV8Env) {
      try {
        const { versions, arch, platform, execPath } = system.process;
        a = versions, b = arch, c = platform, d = execPath
        // systenKey = Buffer.from(`${a + b + c + d}`).toString('base64')
        systenKey = btoa(encodeURIComponent("" + (a + b + c + d)));
      } catch (error) {

      }
    }
    // 保存当前方法
    let staging = desc.value;
    desc.value = function (...datas: any[]) {
      staging.call(this, { systenKey, browserEnv, NodeV8Env })
    }
  }
}



/**
 *  @description 获取当前运行环境
 *  @constructor
 *  @example
 * let runtimes = new runtime() 
 * // 运行返回结果
 * runtimes： {
  "Envet": {
    "systenKey":"TW96aWxsYU5ldHNjYXBlNS4wJTIwKFdpbmRvd3MlMjBOVCUyMDEwLjAlM0IlMjBXaW42NCUzQiUyMHg2NCklMjBBcHBsZVdlYktpdCUyRjUzNy4zNiUyMChLSFRNTCUyQyUyMGxpa2UlMjBHZWNrbyklMjBDaHJvbWUlMkYxMDAuMC40ODk2LjEyNyUyMFNhZmFyaSUyRjUzNy4zNiUyMEVkZyUyRjEwMC4wLjExODUuNDR6aC1DTiUyQ2VuJTJDZW4tR0IlMkNlbi1VUw==", // 根据当前环境生成的字符串
    "browserEnv": true,// 浏览器环境
    "NodeV8Env": false  // NodeJS环境
  }
}
 *  @example
new runtime().browser() // { version: 'v16.0.0', arch: 'x64', platform: 'win32' }

 */
class runtime {
  public Envet!: systemType;
  constructor() {
    this.defaultSecretKey()
  }
  /**
 * 获取当前运行时
 * @param window 
 * @returns {browserEnv:浏览器运行时,NodeV8Env:Node运行时,systenKey:根据版本信息生成字符串}
 */
  @systemV8AndNodeOS(globalThis)
  private defaultSecretKey(window?: any) {
    this.Envet = window
  }
  /**
   * @description 获取当前运行时 如Node 或浏览器运行的版本号和系统
   * @returns { version: 'v16.0.0', arch: 'x64', platform: 'win32' }
   */
  public browser(): runTimeBrowser {
    if (this.Envet.browserEnv) {
      const envet: string = navigator.userAgent, reg = /\((.+?)\)/gi, endwind = envet.match(reg) || [];
      let replaces: string = '';
      const strReapce = (strAr: Array<string>, endwind: string) => {
        replaces = endwind.replace(strAr[0], "")
        for (let i = 1; i < strAr.length; i++) {
          strReapce([strAr[i]], replaces)
        }
        return replaces
      }
      strReapce(endwind, envet)

      const filtnull = replaces?.replace(' ', ',')?.split(',')
      const mobile = filtnull.find((i: string | string[]) => i.includes('Mobile'))
      const [platform, arch, version] = filtnull.filter((i: string) => i !== '')
      return {
        version,
        arch,
        platform,
        mobile,
        mobileInfo: endwind[0]
      }
    } else {
      const { version, arch, platform } = process
      return {
        version,
        arch,
        platform
      }
    }
  }
}


//防抖：在事件被触发n秒后再执行回调，如果在这n秒内又被触发，则重新计时。 
export const debounce = (fun: Function, delay = 200) => {
  return function (args: any) {
    // @ts-expect-error
    let that = this, _args = args, stopId: number | undefined | setTimeout;
    clearTimeout(stopId);
    stopId = setTimeout(() => {
      fun.call(that, _args)
    }, delay)
  }
}

//节流：规定在一个单位时间内，只能触发一次函数。如果这个单位时间内触发多次函数，只有一次生效。
export const throttle = (fn: Function, delay = 200) => {
  let timer = true;
  return function (args: any) {
    // @ts-expect-error
    let that = this, _args = arguments;
    if (!timer) {
      return false;
    }
    timer = false;
    const stopTimeout = setTimeout(() => {
      fn.apply(that, _args)
      timer = true;
      clearTimeout(stopTimeout)
    }, delay)
  }
}

/**
* 判断数据源是否为空 或者为假
* @param { data } Array-Object
* @return { Boolean}
*/
function isNull(data: Array<Number | String>): Boolean;
function isNull(data: Object): Boolean;
function isNull(data: string): Boolean;
function isNull(data: Array<Number | String> | Object | string) {
  const nulldata = ['', null, 'null', undefined, 'undefined']
  const set: Set<string | null | undefined> = new Set(nulldata)
  if (data instanceof Array) {
    if (data.length === 0) return true
    if (data.length > 0) {
      const fd = data.filter((i: any) => nulldata.includes(i))
      return fd.length === 0 ? false : true
    } else {
      return false
    }
  } else if (data instanceof Object) {
    return Object.keys(data).length >= 0
  } else {
    return set.has(data)
  }
}
// const isNull = (data: any): boolean => {
//   const set = new Set(['', null, 'null', undefined, 'undefined'])
//   return set.has(data)
// }



/**
* 判断当前是不是一个函数 如果是就执行,
* @param Fun
* @param objFunc
*/

export const isFunc = (Fun: () => void, ...objFunc: any[]) => {
  if (Fun instanceof Function) {
    Fun();
    if (Array.isArray(objFunc)) {
      for (let key in objFunc) {
        objFunc[key] instanceof Function && objFunc[key]();
      }
    }
  }
}

/**
 * 匹配字符中是否存在多个http地址
 * @param {String}} str
 * 返回一个对象 true 为一个 false 为个
 */
export const MatchingProtocol = (str: { match: (arg0: RegExp) => null; }) => {
  if (!str) return false;
  const regex = /http:/g;
  const result: any = str.match(regex) === null ? str.match(/https:/g) : str.match(regex);
  // 当前连接里面只有一HTTP或者HTTPS，返回true 否则返回false 在返回当前的图片
  return {
    many: result && result.length === 1 ? true : false,
    list: result
  };
}
/**
* 倒计时
* @param item 倒计时时间
* @param running 正在倒计时中得会执行得函数
* @param finish 倒计时结束执行得函数
*/
export const countDown = (item = 60, running: (arg0: number, arg1: number) => any, finish: (arg0: number, arg1: number) => any) => {
  let stop = setInterval(() => {
    if (item <= 0) {
      clearInterval(stop);
      running instanceof Function && running(stop as unknown as number, item);
      finish instanceof Function && finish(stop as unknown as number, item);
    } else {
      running instanceof Function && running(stop as unknown as number, item);
    }

    item--;
  }, 1000);
}

/**
* 返回当前数据的数据类型
* @param data 数据源
* @returns 
*/
export const isType = (data: any) => {
  return ({
    '[object Object]': 'Object',
    '[object Number]': 'Number',
    '[object Array]': 'Array',
    '[object Boolean]': 'Boolean',
    '[object Null]': 'Null',
    '[object Undefined]': 'Undefined',
    '[object String]': 'String',
    '[object Symbol]': 'Symbol',
    '[object Date]': 'Date',
    '[object BigInt]': 'BigInt',
    '[object Function]': 'Function',
  }[{}.toString.call(data)])
}
/**
 * 判断是否是 16 进制颜色
 */
export const hexadecimal = (color: string): boolean => {
  if (isNull(color)) return false;
  // if (!isNull(color)) {
  const set = new Set(["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f", '#']), colorS = color.split(''), isList: boolean[] = [];
  //toUpperCase 小写转大写  toLowerCase大写转小写
  colorS.forEach(item => { isList.push(set.has(item.toLowerCase())) });
  return isList.every(item => item === true);
  // }
}


/**
 *  # 自定义随机数反回
 * @param n 随机的位数 默认为 6 
 * @param cb 可选的回调函数
 * @returns String
 * ### 代码示例
 *  ```javascript
    randomCoding(4) //jo8LFl
    randomCoding() //Qlzf96
  ```
   ```javascript
    UtilityBox.randomCoding(4, (data) => {
    console.log(data);
  })
  ```
 */

export const randomCoding = (n = 6, callBack?: Function): string | string => {
  //创建26个字母数组
  type Array_Str = Array<string>
  let z: Array_Str = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'], //toUpperCase() 转大写
    Z: Array_Str = ['z', 'b', 'c', 'c', 'e', 'f', 'g', 'h', 'i', 'j', 'l', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'], //toLowerCase() 转小写
    num: Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], arr: Array<number | string> = [...num, ...z, ...Z], idvalue = ''; //这个值可以改变的，对应的生成多少个字母，根据自己需求所改
  for (let i = 0; i < n; i++) {
    idvalue += arr[Math.floor(Math.random() * arr.length)];
  }
  const d: randomCodingCallback = { idvalue, z, Z, arr, num }
  callBack && callBack(d)
  return idvalue;
}
/**
 * 伪类数组鉴别以及转换
 * @param {Object} ar
 * @return {true/false}
 */
export const ToIdentify = (ar: Array<any> | any) => ar.push || ar.pop || ar.map || ar.shift === undefined ? [...ar] : ar;



/**
 * 地图 或者 经纬度先关
 * ### 代码示例
 * ```javascript
 * // 百度地图经纬度 转 腾讯地图经纬度转
 * new UtilityBox.MapUtil().bMapToQQMap("106.592539", "29.57011") // { lng: 106.586034, lat: 29.564201 }
 * ```
 * 
 */
class MapUtil {
  constructor() {

  }
  //百度地图经纬度 转 腾讯地图经纬度转
  bMapToQQMap(lng: string, lat: string) {
    if (lng == null || lng == '' || lat == null || lat == '') return [lng, lat];

    let x_pi = 3.14159265358979324,
      x = parseFloat(lng) - 0.0065,
      y = parseFloat(lat) - 0.006,
      z = Math.sqrt(x * x + y * y) - 0.00002 * Math.sin(y * x_pi),
      theta = Math.atan2(y, x) - 0.000003 * Math.cos(x * x_pi);
    const lng1 = (z * Math.cos(theta)).toFixed(6),
      lat1 = (z * Math.sin(theta)).toFixed(6);
    return {
      lng: parseFloat(lng1),
      lat: parseFloat(lat1)
    }
  }


  /**
  * 计算经纬度差值
  * @param lat1 我当前经度
  * @param lng1 我当前纬度
  * @param lat2 企业的经度
  * @param lng2 企业的纬度
  */
  positionToCalculate(lat1: any, lng1: any, lat2: any, lng2: any): string {
    const Rad = (d: number) => {
      return (d * Math.PI) / 180.0; // 经纬度转换成三角函数中度分表形式。
    };
    let radLat1 = Rad(lat1), radLat2 = Rad(lat2), a = radLat1 - radLat2, b = Rad(lng1) - Rad(lng2);
    let s =
      2 *
      Math.asin(
        Math.sqrt(
          Math.pow(Math.sin(a / 2), 2) +
          Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b / 2), 2)
        )
      );
    s = s * 6378.137; // EARTH_RADIUS;
    s = Math.round(s * 1000) / 1000; // 这里输出米   //输出为公里为 1Math.round(s * 10000) / 10000;
    let result = s.toString().split(".").map((i) => parseFloat(i));
    const [Km, M] = result;
    return Km == null || Km === 0 ? M + "米" : s + "公里";
  }
}

// 深拷贝 使用 WeakMap 解决栈溢出
const deeps = (data: string | number | object | undefined | any, newWeakMap = new WeakMap()) => {
  // 第一天条件包含了 null 和 undefined 因为这里不是严格判断
  // 第一个条件  undefined (声明没有赋值) 和 null (一个空指针对象,把一个变量设置为 null 会被垃圾回收机制回收) 在if语句中，都会被自动转为false
  // 第二个条件 判断是否是引用类型  这是使用 typeof来比较得   typeof 检测引用类型有缺点 区分不出来引用类型
  if (data == undefined || typeof data !== "object") {
    return data;
  }

  // 使用 instanceof 来检测引用类型 function、Array、Date，RegExp。
  if (data instanceof Date) return new Date(data);
  if (data instanceof RegExp) return new RegExp(data);

  // 上面拦截了引用类型 用当前得 类型得构造函数创建新的对象

  // 获取当前得 WeakMap 里面是否有对应得值
  // WeakMap集合键必须是一个对象 上面有提到
  const hasMap = newWeakMap.get(data);

  // 当前得WeakMap 中存在值 就返回 不存在继续往下面走
  if (hasMap) return hasMap;

  // 根据当前得得数据类型来构造新的对象
  const newOrg = new data.constructor();

  // 在 WeakMap 中设置值
  newWeakMap.set(data, newOrg);

  for (const key in data) {
    // 检测当前对象是否是存在这个key
    if (data.hasOwnProperty(key)) {
      newOrg[key] = deeps(data[key], newWeakMap);
    }
  }
  return newOrg;
}


export {
  runtime,
  MapUtil,
  deeps,
}

export default { runtime, MapUtil, deeps, randomCoding, hexadecimal, isType }